package top.malaoshi.jdbc;

import top.malaoshi.easy.logging.Log;
import top.malaoshi.easy.logging.LogFactory;

import java.beans.IntrospectionException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class DbImpl implements Db{
    private Log LOG = LogFactory.getLog(DbImpl.class);

    protected Connection connection;

    public DbImpl() {
    }

    public DbImpl(Connection connection) {
        this.connection = connection;
    }

    /**
     * 查询
     * @param sql
     * @param objects
     * @throws SQLException
     */
    @Override
    public List<Record> query(String sql, Object ... objects ) throws SQLException{
        PreparedStatement pstm = null;
        ResultSet rs = null;
        List<Record> retList = null;
        try {
            pstm = prepared(sql, objects);

            rs = pstm.executeQuery();

            retList = resolveResultSetToList(rs);
        }finally {
            //查询后要释放资源
            close(rs,pstm);
        }
        return retList;
    }

    /**
     * 指定封装类，将查询的结果封装到该类中
     * 注意：列名要与类成员变量名相同，才能封装
     *
     * @param sql
     * @param params
     * @param clazz 指定封装类
     * @return
     * @throws SQLException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     * @throws InvocationTargetException
     */
    @Override
    public <E> List<E> query(Class clazz,String sql,Object ... params) throws SQLException {
        List retList = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        try {
            pstm = prepared(sql, params);
            // 然后执行查询
            rs = pstm.executeQuery();
            retList = resolveResultSetToBean(clazz, rs);
        }finally {
            close(rs,pstm);
        }
        return retList;
    }

    /**
     * 增删改
     * @param sql
     * @param objects
     * @return
     * @throws SQLException
     */
    @Override
    public int update(String sql,Object ... objects) throws SQLException {
        PreparedStatement ps = null;
        int i = 0;
        try {
            ps = prepared(sql, objects);
            i = ps.executeUpdate();
        }finally {
            if(ps!=null) {
                ps.close();
            }
        }
        return i;
    }

    /**
     * sql语句使用  #{} 占位符
     * 从sql中解析出 keyword，根据keyword，从obj中获取同名的成员变量值
     * @param sql
     * @param params
     * @return
     * @throws SQLException
     */
    @Override
    public int updateTag(String sql , Object params ) throws SQLException {
        int i = 0;
        PreparedStatement pstm = null;
        try {
            if(params instanceof Map){
                Map map = (Map) params;
                pstm = preparedTag(sql,map);
            }else {
                pstm = preparedTag(sql, params);
            }
            i = pstm.executeUpdate();
        }finally {
            if(pstm!=null) {
                pstm.close();
            }
        }
        return i;
    }
    /**
     *
     * sql语句使用  #{} 占位符
     * @param sql
     * @param params：可以是bean，也可以是Map类型
     * @return List，里面的元素是 Record 类型
     * @throws SQLException
     */
    @Override
    public List<Record> queryTag(String sql, Object params) throws SQLException {
        PreparedStatement pstm = null;
        ResultSet rs = null;
        List<Record> retList = null;
        try {
            if (params instanceof Map) {
                Map map = (Map) params;
                pstm = preparedTag(sql, map);
            } else {
                pstm = preparedTag(sql, params);
            }
            rs = pstm.executeQuery();

            retList = resolveResultSetToList(rs);
        }finally {
            //查询后要释放资源
            close(rs,pstm);
        }
        return retList;
    }

    /**
     *
     * sql语句使用  #{} 占位符
     * 从sql中解析出 #{} 中的keyword，根据keyword，从obj中获取同名的成员变量值
     * @param clazz
     * @param sql
     * @param params
     * @return List，里面的元素是 Class 类型
     * @throws SQLException
     */
    @Override
    public <E> List<E> queryTag(Class clazz,String sql, Object params) throws SQLException {
        List retList = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        try {
            if (params instanceof Map) {
                Map map = (Map) params;
                pstm = preparedTag(sql, map);
            } else {
                pstm = preparedTag(sql, params);
            }
            rs = pstm.executeQuery();
            retList = resolveResultSetToBean(clazz, rs);
        }finally {
            //查询后要释放资源
            close(rs,pstm);
        }
        return retList;
    }

    @Override
    public void close() throws SQLException{
        if(connection !=null){
            connection.close();
        }
    }
    @Override
    public void commit() throws SQLException{
        if(connection != null) {
            connection.commit();
        }
    }
    @Override
    public void rollback() throws SQLException {
        if(connection != null) {
            connection.rollback();
        }
    }
    @Override
    public void setConnection(Connection connection) {
        this.connection = connection;
    }
    @Override
    public Connection getConnection() {
        return connection;
    }

    /**
     * 从 ResultSet 中解析结果，封装到 List<Record> 中
     * @param rs
     * @return
     * @throws SQLException
     */
    private List<Record> resolveResultSetToList(ResultSet rs) throws SQLException {
        List retList=new ArrayList();

        ResultSetMetaData rsmd=rs.getMetaData();
        int count=rsmd.getColumnCount();//动态返回查询的列数
        while(rs.next()){
            Record record=new Record();
            for(int i=0   ; i<count   ; i++  ){
                record.put(rsmd.getColumnLabel(i+1), rs.getObject(i+1));
            }
            //将数组放入到List中
            retList.add(record);
        }
        return retList;
    }
    /**
     * 从连接中获取 PreparedStatement ，并绑定参数
     * 从sql中解析出 keyword，根据keyword，从obj中获取同名的成员变量值
     * @param sql
     * @param obj
     * @return
     * @throws SQLException
     * @throws NoSuchFieldException
     */
    private PreparedStatement preparedTag(String sql, Object obj) throws SQLException {
        LOG.debug("SQL:"+sql);
        Class<?> objClass = obj.getClass();
        TagSQLParser p=new TagSQLParser(sql);
        String selectSQL=p.parse();
        List<String> keywords=p.getKeywords();

        if( LOG.isDebugEnabled() ){
            LOG.debug("parameter:");
            for(int i=0; i<keywords.size(); i++){
                String keyword=keywords.get(i);
                // 根据keyword，从obj中获取同名的成员变量值
                Field field = null;
                try {
                    field = objClass.getDeclaredField(keyword);
                } catch (NoSuchFieldException e) {
                    throw new ReflectionException(e);
                }
                field.setAccessible(true);
                Object param = null;
                try {
                    param = field.get(obj);
                } catch (IllegalAccessException e) {
                    throw new ReflectionException(e);
                }

                LOG.debug("\t"+ keyword +":"+param);
            }
        }

        PreparedStatement pstm= connection.prepareStatement(selectSQL);
        for(int i=0; i<keywords.size(); i++){
            String keyword=keywords.get(i);
            // 根据keyword，从obj中获取同名的成员变量值
            Field field = null;
            try {
                field = objClass.getDeclaredField(keyword);
            } catch (NoSuchFieldException e) {
                throw new ReflectionException(e);
            }
            field.setAccessible(true);
            Object param = null;
            try {
                param = field.get(obj);
            } catch (IllegalAccessException e) {
                throw new ReflectionException(e);
            }
//            Object param=params.get(keyword );
            pstm.setObject(i+1, param);
        }
        return pstm;
    }
    private void close(ResultSet rs,PreparedStatement pstm) throws SQLException{
        if(rs!=null) {
            try {
                rs.close();
            } catch (SQLException e) {
            }
        }
        if(pstm!=null) {
            try {
                pstm.close();
            } catch (SQLException e) {
            }
        }
    }
    private PreparedStatement preparedTag(String sql, Map<String, Object> params) throws SQLException{
        LOG.debug("SQL:"+sql);
        TagSQLParser p=new TagSQLParser(sql);
        String selectSQL=p.parse();
        List<String> keywords=p.getKeywords();
        if( LOG.isDebugEnabled() ){
            LOG.debug("parameter:");
            for(int i=0; i<keywords.size(); i++){
                String keyword=keywords.get(i);
                Object param=params.get(keyword );
                LOG.debug("\t"+ keyword +":"+param);
            }
        }
        PreparedStatement pstm= connection.prepareStatement(selectSQL);
        for(int i=0; i<keywords.size(); i++){
            String keyword=keywords.get(i);
            Object param=params.get(keyword );
            pstm.setObject(i+1, param);
        }
        return pstm;
    }
    /**
     * 从连接创建 PreparedStatement，并且绑定参数
     * @param sql
     * @param objects
     * @return
     * @throws SQLException
     */
    private PreparedStatement prepared(String sql,Object ... objects) throws SQLException {
        LOG.debug("SQL:"+sql);
        LOG.debug("parameter:"+ Arrays.toString(objects));
        PreparedStatement ps= connection.prepareStatement(sql);
        if(objects!=null && objects.length>0){
            for(int i=0;i<objects.length;i++){
                ps.setObject(i+1, objects[i]);
            }
        }
        return ps;
    }
    /**
     * 解析 ResultSet，并封装成 bean
     * @param clazz
     * @param resultSet
     * @return
     * @throws SQLException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws IntrospectionException
     * @throws NoSuchFieldException
     * @throws InvocationTargetException
     */
    private List resolveResultSetToBean(Class clazz,ResultSet resultSet) throws SQLException{

        List retList = new ArrayList();
        Map<String,Method> propertyAndSetterMap = null;
        try {
            propertyAndSetterMap = IntrospectorUtils.getSetMethods(clazz);
        } catch (IntrospectionException e) {
            throw new ReflectionException(e);
        } catch (NoSuchFieldException e) {
            throw new ReflectionException(e);
        }
        ResultSetMetaData rsmd = resultSet.getMetaData();
        // 返回查询结果列的总数
        int columnNum = rsmd.getColumnCount();
        while (resultSet.next()){
            // 通过反射方式实例化
            Object bean = null;
            try {
                bean = clazz.newInstance();
            } catch (InstantiationException e) {
                throw new ReflectionException(e);
            } catch (IllegalAccessException e) {
                throw new ReflectionException(e);
            }
            // 将查询列的值，放入到 javabean（模型类）的 属性中
            for( int i = 1 ;i<=columnNum ;i++){

                // 取出查询的列名
                String columnLabel = rsmd.getColumnLabel(i);
                // 取出与该列名相应的值
                Object value = resultSet.getObject(i);
                // 根据列名，从map中取出与列名相同的属性的set方法
                Method setMethod = propertyAndSetterMap.get(columnLabel);
                // 如果方法为null，表示没有对应的方法，就不再往下执行
                if(setMethod == null) {
                    continue;
                }
                invokeSetMethod(bean,setMethod,value);
            }
            retList.add(bean);
        }
        return retList;
    }

    /**
     * 调用 setter 方法
     * @param bean
     * @param setMethod
     * @param value
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private void invokeSetMethod(Object bean, Method setMethod, Object value ) {


        Object dataTemp = null;
        try {
            // 将从数据库查询出来的数据类型，转成 setter 方法的形参类型
            dataTemp = ReflectionUtil.converte(setMethod, value);
        } catch (NoSuchMethodException e) {
            throw new ReflectionException(e);
        } catch (InvocationTargetException e) {
            throw new ReflectionException(e);
        } catch (InstantiationException e) {
            throw new ReflectionException(e);
        } catch (IllegalAccessException e) {
            throw new ReflectionException(e);
        }
        // 动态调用 setXXX 方法，并且传值
        try {
            setMethod.invoke(bean,dataTemp);
        }catch (java.lang.IllegalArgumentException e){
            String dataType = "null";
            if(dataTemp!=null){
                dataType = dataTemp.getClass().getName();
            }
            String msg = String.format("%s() 形参类型是：%s，从数据库查询出来的数据：%s，类型是：%s，类型不匹配",
                    setMethod.getName(),
                    setMethod.getParameterTypes()[0],
                    dataTemp,
                    dataType);

            throw new IllegalArgumentException(msg);
        } catch (InvocationTargetException e) {
            throw new ReflectionException(e);
        } catch (IllegalAccessException e) {
            throw new ReflectionException(e);
        }
    }

}
